#include <unittest++/UnitTest++.h>
#include <Ape/Collections/Array.h>
#include <Ape/IO/FS/RegularFile.h>
#include <Ape/Exception.h>
#include <Ape/IO/FS/EFileNotFound.h>
#include <Ape/EInvalidOperation.h>
#include <Ape/EIndexOutOfRange.h>
#include <Ape/Serialization/ISerializeable.h>
#include <Ape/IO/Console.h>
#include <Ape/Int.h>

#define TEMP_DIR L"/tmp"

namespace ApeTest {
	namespace IO {
		namespace FS {
			using namespace Ape::IO::FS;
			using Ape::Collections::Array;
			
			/**
			 * Here we check basic file functions
			 */
			TEST (Ape_IO_FS_RegularFile_WriteModeTest) {
				RegularFile f1 (TEMP_DIR L"/Ape_IO_FS_RegularFile_WriteModeTest.tmp");
				f1.Open (oWrite);
				
				// Explicit close
				f1.Close();
				struct stat f1Stat = f1.get_Stat();
				f1.Unlink();
				f1.Unlink (true);
				CHECK_THROW (f1.Unlink(), EFileNotFound);
				
				// Here the file's destructor is called it will try to close the file again.
			}
			
			/**
			 * Here we check simultaneous write and read access
			 */
			TEST (Ape_IO_FS_RegularFile_BinaryWriteAndReadTest) {
				RegularFile f1 (TEMP_DIR L"/Ape_IO_FS_RegularFile_BinaryWriteAndReadTest.tmp");
				RegularFile f2 (TEMP_DIR L"/Ape_IO_FS_RegularFile_BinaryWriteAndReadTest.tmp");
				
				f1.Open (oWriteBinary);
				f2.Open (oReadBinary);
				
				// let us clean up here
				// further in this test we will use this file (actually i-node) after it's deletion
				// it will exist till all handles are closed
				f1.Unlink();
				// let us provoke an exception
				CHECK_THROW (f2.Unlink(), EFileNotFound);
				
				RegularFile::Buffer_t buffWrite (20, 'w');
				RegularFile::Buffer_t buffRead (10, 'a');
				int actuallyWritten = f1.Write (buffWrite);
				CHECK_EQUAL (20, actuallyWritten); // we've just written 20 bytes
				f1.Flush();
				int actuallyRead = f2.Read (buffRead, 5);
				// we forced to use only first five bytes of the array
				CHECK_EQUAL (5, actuallyRead);
				for (int i = 0; i < 5; i++) {
					// first five bytes of the array should be read from the file
					CHECK_EQUAL ('w', buffRead[i]);
				}
				for (int i = 5; i < 10; i++) {
					// the others should remain as during initialization
					CHECK_EQUAL ('a', buffRead[i]);
				}
				
				// we ask the reader to fill the array as much as it can
				// (array size - 10; bytes left - 15 => we should get 10 bytes read)
				actuallyRead = f2.Read (buffRead);
				// 10 bytes ?
				CHECK_EQUAL (10, actuallyRead);
				for (int i = 0; i < 10; i++) {
					// every byte should be read from the file - and be equal to 'w'
					CHECK_EQUAL ('w', buffRead[i]);
				}
				
				// we again ask the reader to fill the array as much as possible
				// (array size - 10; bytes left - 5 => we should get 5 bytes read)
				actuallyRead = f2.Read (buffRead);
				CHECK_EQUAL (5, actuallyRead);
				for (int i = 0; i < 5; i++) {
					// first five bytes of the array should be read from the file
					CHECK_EQUAL ('w', buffRead[i]);
				}
				for (int i = 5; i < 10; i++) {
					// the others should be set to '\0'
					CHECK_EQUAL ('\0', buffRead[i]);
				}
			}
			
			/**
			 * We try to write here from two different handles into different regions of the file
			 * The reader controls if everything is okay
			 * The file should look like that - '111111111122222222221111111111':
			 * first writer puts 30 bytes of '1' and the second writer puts 10 bytes of '2' beginning from 10th pos.
			 */
			TEST (Ape_IO_FS_RegularFile_BinaryWriteUsingTwoHandles) {
				RegularFile fw1 (TEMP_DIR L"/Ape_IO_FS_RegularFile_BinaryWriteUsingTwoHandles.tmp");
				RegularFile fw2 (TEMP_DIR L"/Ape_IO_FS_RegularFile_BinaryWriteUsingTwoHandles.tmp");
				
				RegularFile fr (TEMP_DIR L"/Ape_IO_FS_RegularFile_BinaryWriteUsingTwoHandles.tmp");
				
				fw1.Open (oWriteBinary);
				fw2.Open (oWriteBinary);
				
				fr.Open (oReadBinary);
				
				RegularFile::Buffer_t buffWrite1 (30, '1');
				RegularFile::Buffer_t buffWrite2 (10, '2');
				RegularFile::Buffer_t buffRead (10, '\0');
				
				fw1.Write (buffWrite1);
				fw1.Flush();
				
				fw2.Seek (10, fromStart);
				fw2.Write (buffWrite2);
				fw2.Flush();
				
				int actuallyRead = fr.Read (buffRead);
				CHECK_EQUAL (10, actuallyRead);
				for (int i = 0; i < 10; i++) {
					CHECK_EQUAL ('1', buffRead[i]);
				}
				actuallyRead = fr.Read (buffRead);
				CHECK_EQUAL (10, actuallyRead);
				for (int i = 0; i < 10; i++) {
					CHECK_EQUAL ('2', buffRead[i]);
				}
				actuallyRead = fr.Read (buffRead);
				CHECK_EQUAL (10, actuallyRead);
				for (int i = 0; i < 10; i++) {
					CHECK_EQUAL ('1', buffRead[i]);
				}
				
				fw1.Unlink();
			}
			
			TEST (Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes) {
				RegularFile fwb (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				fwb.Open (oWriteBinary);
				RegularFile frb (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				frb.Open (oReadBinary);
				RegularFile fab (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				fab.Open (oAppendBinary);
				RegularFile fwt (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				fwt.Open (oWrite);
				RegularFile frt (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				frt.Open (oRead);
				RegularFile fat (TEMP_DIR L"/Ape_IO_FS_RegularFile__ExceptionsCausedByIncorrectModes.tmp");
				fat.Open (oAppend);
				
				RegularFile::Buffer_t buffer (128, '\0');
				buffer[127] = '\n';
				int cnt = 0;
				
				cnt = fwb.Write (buffer);
				cnt = fab.Write (buffer);
				cnt = fwt.Write (buffer);
				cnt = fat.Write (buffer);
				cnt = frb.Read (buffer);
				cnt = frt.Read (buffer);
				
				CHECK_THROW (cnt = fwb.Read (buffer), Ape::EInvalidOperation);
				CHECK_THROW (cnt = fwt.Read (buffer), Ape::EInvalidOperation);
				CHECK_THROW (cnt = fab.Read (buffer), Ape::EInvalidOperation);
				CHECK_THROW (cnt = fat.Read (buffer), Ape::EInvalidOperation);
				CHECK_THROW (cnt = frb.Write (buffer), Ape::EInvalidOperation);
				CHECK_THROW (cnt = frt.Write (buffer), Ape::EInvalidOperation);
				
				fwb.Unlink();
			}
		}
	}
}

#undef TEMP_DIR
